/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

// draw.c -- this is the only file outside the refresh that touches the
// vid buffer

#include "quakedef.h"

// to do - make gl_texturemode a cvar

#define MAX_D3D_TEXTURES		2048


QTEXTURE d3d_Textures[MAX_D3D_TEXTURES];
int d3d_NumTextures = 0;

// this will be the current scrap texture
static QTEXTURE *d3d_ScrapTexture = NULL;

QTEXTURE::QTEXTURE (void)
{
	// clear down everything in case we allocate this on the stack
	this->Texture = NULL;
	this->SRV = NULL;
	this->RTV = NULL;
	this->DSV = NULL;
	this->Identifier[0] = 0;
}


void QTEXTURE::UpdateTexture (byte *Data, int Width, int Height, int Flags)
{
	// this can happen if we use a texture as a DSV or RTV
	if (!this->Texture) Sys_Error ("QTEXTURE::UpdateTexture : attempt to update a NULL texture!!!!!!!!!");

	// get the texture description so that we know how many miplevels it should have
	D3D10_TEXTURE2D_DESC desc;
	this->Texture->GetDesc (&desc);

	// sanity check for stuff we can't do or don't bother supporting
	if (desc.Usage == D3D10_USAGE_IMMUTABLE) Sys_Error ("QTEXTURE::UpdateTexture : attempt to update an immutable texture!!!!!!!!!");
	if (desc.ArraySize > 1) Sys_Error ("QTEXTURE::UpdateTexture : attempt to update a texture array");
	if (desc.MiscFlags & D3D10_RESOURCE_MISC_TEXTURECUBE) Sys_Error ("QTEXTURE::UpdateTexture : attempt to update a cube map");

	int mark = qScratch->GetLowMark ();
	unsigned *trans = R_ExpandTexture (Data, Width, Height, Flags);

	// can this ever happen???
	if (Width != desc.Width || Height != desc.Height)
	{
		trans = GL_ResampleTexture (trans, Width, Height, desc.Width, desc.Height);
		Width = desc.Width;
		Height = desc.Height;
	}

	// fill the texture level 0
	d3d_Device->UpdateSubresource (this->Texture, 0, NULL, trans, Width << 2, 0);

	// if the texture isn't mipmapped then none of this loop will run
	for (int miplevel = 1; miplevel < desc.MipLevels; miplevel++)
	{
		// generate this miplevel
		trans = R_MipMap (trans, Width, Height);

		// and load it
		d3d_Device->UpdateSubresource (this->Texture, miplevel, NULL, trans, Width << 2, 0);
	}

	// flush the device to bring all creation up to date before throwing away our memory
	d3d_Device->Flush ();

	// now it's safe to throw things away
	qScratch->FreeToLowMark (mark);
}


void QTEXTURE::CreateTexture (byte *Data, int Width, int Height, int Flags, int ArraySize)
{
	// the resource may have already existed so kill it now
	SAFE_RELEASE (this->Texture);
	SAFE_RELEASE (this->SRV);
	SAFE_RELEASE (this->RTV);
	SAFE_RELEASE (this->DSV);

	int nummips = 1;
	int mark = qScratch->GetLowMark ();

	// figure how many miplevels we have
	while ((Width | Height) >> nummips) nummips += 1;

	// we only create an immutable texture if data was specified
	CD3D10_TEXTURE2D_DESC desc (
		DXGI_FORMAT_R8G8B8A8_UNORM,
		Width,
		Height,
		(Flags & TEX_CUBEMAP) ? 6 : ArraySize,	// array size
		(Flags & TEX_MIPMAP) ? nummips : 1,
		D3D10_BIND_SHADER_RESOURCE,
		((Flags & TEX_IMMUTABLE) && Data) ? D3D10_USAGE_IMMUTABLE : D3D10_USAGE_DEFAULT,
		0,
		1,
		0,
		(Flags & TEX_CUBEMAP) ? D3D10_RESOURCE_MISC_TEXTURECUBE : 0
	);

	D3D10_SUBRESOURCE_DATA *srd = NULL;

	// load at creation time if data was supplied for the texture; otherwise it's just to be filled later
	if (Data) srd = R_CreateTextureSRDs (Data, Width, Height, desc.ArraySize, desc.MipLevels, Flags);

	// create the texture without any initial data
	if (FAILED (d3d_Device->CreateTexture2D (&desc, srd, &this->Texture))) Sys_Error ("QTEXTURE::CreateTexture : CreateTexture2D failed");

	// and it's shader resource view
	if (FAILED (d3d_Device->CreateShaderResourceView (this->Texture, NULL, &this->SRV))) Sys_Error ("QTEXTURE::CreateTexture : CreateShaderResourceView failed");

	// if the texture was created immutable we can throw it away now; this works because creating a view will
	// hold a reference on the texture object, and we don't want anyone accessing immutable textures directly
	if (Flags & TEX_IMMUTABLE)
	{
		// any attempt to access the texture of an immutable texture is an error
		this->Texture->Release ();
		this->Texture = NULL;
	}

	// flush the device to bring all creation up to date before throwing away our memory
	d3d_Device->Flush ();

	// now it's safe to throw things away
	qScratch->FreeToLowMark (mark);
}


void QTEXTURE::Release (void)
{
	// release all objects belonging to this texture
	SAFE_RELEASE (this->Texture);
	SAFE_RELEASE (this->SRV);
	SAFE_RELEASE (this->RTV);
	SAFE_RELEASE (this->DSV);

	// clear the identifier to prevent a false match
	this->Identifier[0] = 0;
}


class CTexHandler : public CD3DHandler
{
public:
	void OnReleaseDevice (void)
	{
		// this is separate because it needs to be called from game switching code too
		Tex_Shutdown ();
	}
} d3d_TexHandler;


QTEXTURE *D3D_LoadTexture (char *Identifier, byte *Data, int Width, int Height, int Flags)
{
	if (Flags & TEX_LUMA)
	{
		// ensure that we actually do have a luma
		for (int i = 0; i < Width * Height; i++)
		{
			// don't count alpha texels
			if (Data[i] == 255) continue;

			// check for a luma entry
			if (Data[i] > 223) goto RealLuma;
		}

		// not a luma
		return NULL;

RealLuma:;
	}

	QTEXTURE *glt = NULL;
	unsigned short CRC = Data ? CRC_Block (Data, Width * Height) : 0;

	// see if the texture is allready present
	if (Identifier[0])
	{
		// HACK HACK HACK -- taken from tomazquake
		if (strstr (Identifier, "shot1sid") && Width == 32 && Height == 32 && CRC == 65393)
		{
			// This texture in b_shell1.bsp has some of the first 32 pixels painted white.
			// They are invisible in software, but look really ugly in GL. So we just copy
			// 32 pixels from the bottom to make it look nice.
			memcpy (Data, Data + 32 * 31, 32);
		}

		for (int i = 0; i < d3d_NumTextures; i++)
		{
			glt = &d3d_Textures[i];

			// can never be a match
			if (!glt->Identifier[0]) continue;

			// immutable textures may have their texture object NULLed so we must check the views instead
			if (!glt->SRV && !glt->RTV && !glt->DSV) continue;

			// check early-out conditions before the more expensive strcmp
			if (glt->Width != Width) continue;
			if (glt->Height != Height) continue;
			if (glt->Flags != Flags) continue;
			if (glt->CRC != CRC) continue;

			// and now the more expensive strcmp for final confirmation
			if (strcmp (Identifier, glt->Identifier)) continue;

			// mark as used in this sequence
			glt->RegistrationSequence = r_RegistrationSequence;

			return glt;
		}
	}

	// get a new texture
	// note that the sort after full map load means that free textures are now always at the end of this list
	// so we don't actually need to track free slots any more
	glt = &d3d_Textures[d3d_NumTextures];
	d3d_NumTextures++;

	// fill it in
	strcpy (glt->Identifier, Identifier);
	glt->Width = Width;
	glt->Height = Height;
	glt->Flags = Flags;
	glt->CRC = CRC;

	// mark as used in this sequence
	glt->RegistrationSequence = r_RegistrationSequence;

	// create the new texture
	glt->CreateTexture (Data, Width, Height, Flags);

	// and return what we got
	return glt;
}


void Tex_Shutdown (void)
{
	for (int i = 0; i < MAX_D3D_TEXTURES; i++)
		d3d_Textures[i].Release ();

	for (int i = 0; i < MAX_SCOREBOARD; i++)
	{
		SAFE_RELEASE (PlayerTextures[i].Texture);
		memset (&PlayerTextures[i], 0, sizeof (playertexture_t));
	}

	// fix up these too
	d3d_NumTextures = 0;
	d3d_ScrapTexture = NULL;
}


void D3D_FreeUnusedTextures (void)
{
	for (int i = 0; i < MAX_D3D_TEXTURES; i++)
	{
		// never free these textures
		if (!d3d_Textures[i].Texture) continue;
		if (d3d_Textures[i].Flags & TEX_NODISCARD) continue;

		// allow to persist for 4 map changes
		if (d3d_Textures[i].RegistrationSequence >= (r_RegistrationSequence - 3)) continue;

		// free this texture
		d3d_Textures[i].Release ();
	}
}


QTEXTURE *D3D_ScrapUpload (int width, int height, byte *data, int &scrap_x, int &scrap_y)
{
	int j;
	int best = SCRAP_SIZE;

	static int ScrapAllocated[SCRAP_SIZE];

	// too big
	if (!(width < SCRAP_SIZE && height < SCRAP_SIZE && width * height < 4096))
		return NULL;

	if (!d3d_ScrapTexture)
	{
		// clear allocations
		memset (ScrapAllocated, 0, sizeof (ScrapAllocated));

		// take a new texture for the scrap
		d3d_ScrapTexture = D3D_LoadTexture ("", NULL, SCRAP_SIZE, SCRAP_SIZE, TEX_ALPHA | TEX_NODISCARD);
	}

	for (int i = 0; i < SCRAP_SIZE - width; i++)
	{
		int best2 = 0;

		for (j = 0; j < width; j++)
		{
			if (ScrapAllocated[i + j] >= best) break;
			if (ScrapAllocated[i + j] > best2) best2 = ScrapAllocated[i + j];
		}

		if (j == width)
		{
			// this is a valid spot
			scrap_x = i;
			scrap_y = best = best2;
		}
	}

	if (best + height > SCRAP_SIZE)
	{
		// woops; won't fit in this texture
		d3d_ScrapTexture = NULL;

		// call recursively to get a new texture
		return D3D_ScrapUpload (width, height, data, scrap_x, scrap_y);
	}

	// mark off allocated regions
	for (int i = 0; i < width; i++)
		ScrapAllocated[scrap_x + i] = best + height;

	// fill in the texture
	D3D10_BOX texbox = {scrap_x, scrap_y, 0, scrap_x + width, scrap_y + height, 1};
	int mark = qScratch->GetLowMark ();
	unsigned *trans = (unsigned *) qScratch->FastAlloc (width * height * 4);

	for (int i = 0; i < width * height; i++)
		trans[i] = d_8to24table_rgba[data[i]];

	d3d_Device->UpdateSubresource (d3d_ScrapTexture->Texture, 0, &texbox, trans, width << 2, 0);
	qScratch->FreeToLowMark (mark);

	// and this is now a good texture
	return d3d_ScrapTexture;
}


